﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace LogParseur
{
    /// <summary>
    /// Paramètres déduits :
    ///     -> batch delphi ou .NET (présence de Adp.Pleiade.Fwk.Batch pour .NET)
    ///     -> taille de lots
    /// Arguments obligatoires de la ligne de commande :
    ///     -> 1 : chemin
    ///     -> 2 : fichier de log BA
    /// Options de la ligne de commande
    ///     -> -l[entier] : nombre de lignes générées dans le rapport. Si rien n'est précisé, 
    ///     50 lignes vont être générées, ou un nombre égal au maximum de lignes dans la BA
    ///     -> -s [chemin complet] : génération du rapport .csv. Par défaut, sur C:\
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("Aucun argument n'a été passé. Veuillez utiliser un script .bat pour exécuter cette application");
                Console.ReadKey();
            }
            else
            {
                var parametres = PreparationParametres(args);

                foreach (var jobParam in parametres.JobParams)
                {
                    if (jobParam.Lines != "" && jobParam.PackSize > -1)
                        ParseBA(parametres, jobParam);
                }
            }
        }

        private static Parameters PreparationParametres(string[] args)
        {
            var parametres = new Parameters();

            // Identification des paramètres
            IdentificationParametresObligatoires(args, parametres);

            // Identifcation des paramètres déduits
            IdentificationParametresDeduits(parametres);

            // Identification des paramètres optionnels
            IdentificationParametresOptionnels(args, parametres);

            return parametres;
        }

        private static void IdentificationParametresOptionnels(string[] args, Parameters parametres)
        {
            if (args.Count() > 2)
            {
                foreach (string arg in args)
                {
                    // Gestion du nombre de lignes générées dans le rapport
                    if (arg.Contains("-l"))
                    {
                        int lignes = -1;
                        Int32.TryParse(Regex.Split(arg, "-l")[1], out lignes);
                        parametres.ReportLinesCount = lignes;
                    }
                    else if (arg.Contains("-r"))
                    {
                        parametres.ReportPath = Regex.Split(arg, "-r")[1];
                        if (!Directory.Exists(parametres.ReportPath))
                            Directory.CreateDirectory(parametres.ReportPath);
                    }
                }
            }
        }

        private static void IdentificationParametresDeduits(Parameters parametres)
        {
            if (!parametres.SearchPattern.Contains(".log"))
                parametres.SearchPattern += ".log";

            var files = Directory.GetFiles(parametres.SearchPath, parametres.SearchPattern);

            if (files.Count() > 0)
            {
                foreach (var file in files)
                {
                    var newJobParams = new JobParameters() { LogName = file };

                    using (var sR = new StreamReader(file))
                    {
                        newJobParams.Lines = sR.ReadToEnd();
                        sR.Close();
                    }

                    // Détermination d'une BA
                    var isBAmatches = new Regex("DEMARRAGE BATCH").Matches(newJobParams.Lines);
                    if (isBAmatches.Count > 0)
                    {
                        // Détermination de la taille de lots
                        var matches = new Regex(@"Taille lot: .+").Matches(newJobParams.Lines);
                        if (matches.Count == 1)
                        {
                            int taille = -1;
                            Int32.TryParse(matches[0].Value.Split(':')[1].Trim(), out taille);
                            newJobParams.PackSize = taille;
                        }

                        // Détermination du nombre total d'éléments traités
                        matches = new Regex("Compteur en sortie : Nombre total d'éléments traités = .+").Matches(newJobParams.Lines);
                        if (matches.Count == 1)
                        {
                            int elements = -1;
                            Int32.TryParse(matches[0].Value.Split('=')[1].Trim(), out elements);
                            newJobParams.ProceededElementsCount = elements;
                        }

                        if (newJobParams.PackSize > -1 && newJobParams.Lines != "")
                            parametres.JobParams.Add(newJobParams);
                    }
                }
            }
        }

        private static void IdentificationParametresObligatoires(string[] args, Parameters parametres)
        {
            if (args.Count() > 0)
            {
                parametres.SearchPath = args[0];
                parametres.SearchPattern = args[1];
            }
        }

        static void ParseBA(Parameters parameters, JobParameters jobParametres)
        {
            var packages = PreparerPackagesDotNet(jobParametres);

            // Tri des éléments.
            packages.OrderBy(x => x.DateFinTraitementLot);

            // Alignement du découpage en fonction des packages dispo
            if (parameters.ReportLinesCount > packages.Count)
                parameters.ReportLinesCount = packages.Count;

            if (packages.Count > 0)
            {
                int decoupage = Convert.ToInt32(packages.Count / parameters.ReportLinesCount);

                var shortLogName = Path.GetFileNameWithoutExtension(jobParametres.LogName);

                // Alimentation des stats dans le fichier associé
                using (var sW = new StreamWriter(parameters.ReportPath + shortLogName + ".csv", false))
                {
                    int j = 0;
                    bool termine = false;
                    var total = packages.Count;

                    TraitementPackages(parameters, jobParametres, packages, decoupage, sW, ref j, ref termine, total);

                    sW.Close();
                }
            }
        }

        private static void TraitementPackages(Parameters parametres, JobParameters jobParameters, List<Package> packages, int decoupage, 
            StreamWriter sW, ref int j, ref bool termine, int total)
        {
            // Ecriture de l'entête
            sW.WriteLine("ID Paquet;Duree Paquet(ms);Elements traites;Temps ecoule (s);% total traite;vitesse paquet(elements/h)");

            double tempsTotal = 0;
            int elemementsPackage = 0;
            int elementsAnalyses = 0;
            int nbPackages = packages.Count;
            DateTime firstDate = new DateTime();
            DateTime lastDate = new DateTime();
            bool lastPackageEnded = false;

            while (!termine)
            {
                var count = packages.Count;
                IEnumerable<Package> cup = null;
                
                if (count > decoupage)
                {                    
                    cup = packages.Take(decoupage);
                    elemementsPackage = cup.Count() * jobParameters.PackSize;
                }
                else
                {
                    cup = packages.Take(count);
                    elemementsPackage = jobParameters.ProceededElementsCount - elementsAnalyses;
                }

                firstDate = cup.Min(x => x.DateFinTraitementLot);


                // gestion de l'inter-paquet
                // on ajoute l'intervalle entre la date de fin du paquet précédent et la date de debut du paquet suivant
                if (lastPackageEnded)
                {
                    //if (start < stop)
                    //{
                        tempsTotal += DateDiff(lastDate, firstDate);
                    //}
                            //) start - stop;                   
                }

                lastDate = cup.Max(x => x.DateFinTraitementLot);
                lastPackageEnded = true;

                // total du paquet
                var diff = DateDiff(firstDate, lastDate);
                tempsTotal += diff;
                    //stop - start;

                j++;

                elementsAnalyses += elemementsPackage;

                EcritureDansRapport(jobParameters, packages, decoupage, sW, j, total, count, diff, 
                    tempsTotal, elemementsPackage, elementsAnalyses);

                termine = packages.Count == 0;
            }
        }

        /// <summary>
        /// Calcul d'un intervalle de temps en ms entre deux dates
        /// Gère le changement de jour
        /// </summary>
        /// <param name="first"></param>
        /// <param name="second"></param>
        /// <returns></returns>
        private static double DateDiff(DateTime first, DateTime second)
        {     
            return second.Subtract(first).TotalMilliseconds;
        }

        private static void EcritureDansRapport(JobParameters parametres, List<Package> packages, int decoupage, StreamWriter sW, int j,
            int total, int count, double diff, double tempsTotal, int elemementsPackage, int elementsAnalyses)
        {
            if (count > decoupage)
                packages.RemoveRange(0, decoupage);
            else
                packages.RemoveRange(0, count);     

            sW.WriteLine(
                   j + ";" // id paquet
                   + Convert.ToString(diff.ToString()) + ";" // temps du paquet
                   + elemementsPackage + ";" // éléments traités                                
                   + tempsTotal / 1000 + ";" // temps total ecoule
                   + Convert.ToDouble(elementsAnalyses) / Convert.ToDouble(parametres.ProceededElementsCount) * 100 + ";" // % total traité
                   + elemementsPackage / (diff) * 3600000 + ";" // vitesse paquet
                   );
        }

        private static List<Package> PreparerPackagesDotNet(JobParameters parametres)
        {
            var result = new List<Package>();
            int i = 0;
            Regex regex;
            regex = new Regex(@"\[.+\] Package.+OK");
            MatchCollection selectorMatch = regex.Matches(parametres.Lines);
            var nbPackages = selectorMatch.Count;

            // Alimentation des packages
            foreach (Match item in selectorMatch)
            {
                i++;
                var chaine = item.Value;
                string time = chaine.Replace("[", "").Replace("]", "").Split(' ')[0];

                // récupération du jour
                int year = Convert.ToInt32(time.Split('-')[0].Split('/')[0]);
                int month = Convert.ToInt32(time.Split('-')[0].Split('/')[1]);
                int day = Convert.ToInt32(time.Split('-')[0].Split('/')[2]);              
                
                // récupération de l'heure
                string temps = time.Split('-')[1];
                int hour = Convert.ToInt32(temps.Split(':')[0]);
                int minute = Convert.ToInt32(temps.Split(':')[1]);
                int second = Convert.ToInt32(temps.Split(':')[2].Split('.')[0]);
                int millisecond = Convert.ToInt32(temps.Split(':')[2].Split('.')[1]);

                // construction de la date
                var finaltime = new DateTime(year, month, day, hour, minute, second, millisecond);                 

                result.Add(new Package { IDPackage = i, DateFinTraitementLot = finaltime });
            }
            return result;
        }

        private static void PreparerPackagesDelphi(Parameters parametres, List<Package> packages)
        {
            throw new Exception("Les log BA Delphi ne disposent pas de l'horodatage");
        }

        //static void ParseAllBS()
        //{
        //    Regex regex;
        //    string match = "";
        //    List<Package> packages = new List<Package>();
        //    int i = 0;
        //    int decoupage = 50;

        //    files = Directory.GetFiles(path, "*BA_TraitementDossierFSSExterne_*.log");

        //    foreach (string file in files)
        //    {
        //        var sR = new StreamReader(file);
        //        Package package;
        //        i++;

        //        // Récupération du nombre d'éléments traités par la BS
        //        regex = new Regex(@"Compteur en sortie \: Nombre total d'éléments traités \= {.*}");
        //        match = "";

        //        var chaine = ReadLines(sR).Where(x => x.Contains("Compteur en sortie : Nombre total d'éléments traités =")).FirstOrDefault();

        //        if (!String.IsNullOrEmpty(chaine))
        //        {
        //            var m = Regex.Matches(chaine, @"Nombre total d'éléments traités = (?<nbElements>.+)");
        //            if (m.Count > 0)
        //                match = m[0].Groups["nbElements"].Value;
        //        }

        //        sR.Close();

        //        // Traitement ligne par ligne pour récupérer le contenu d'un lot
        //        sR = new StreamReader(file);
        //        package = new Package();
        //        while (!sR.EndOfStream)
        //        {
        //            chaine = sR.ReadLine();

        //            // Début du package
        //            if (chaine.Contains("Begin Package"))
        //            {
        //                package = new Package();
        //                string temps = chaine.Replace("[", "").Replace("]", "").Split(' ')[0].Split('-')[1];
        //                package.DateDebutTraitementLot = TimeSpan.Parse(temps);
        //            }

        //            // Fin du package
        //            if (chaine.Contains("Fin de traitement du dossier"))
        //            {
        //                string temps = chaine.Replace("[", "").Replace("]", "").Split(' ')[0].Split('-')[1];
        //                package.DateFinTraitementLot = TimeSpan.Parse(temps);
        //                packages.Add(package);
        //            }
        //        }

        //        sR.Close();
        //        // Alimentation des stats dans le fichier associé
        //        StreamWriter sW = new StreamWriter(packages.Count + " elements_BS_" + i + ".csv", false);
        //        int j = 0;
        //        bool termine = false;

        //        var total = packages.Count;
        //        sW.WriteLine(";;0");

        //        while (!termine)
        //        {
        //            var count = packages.Count;
        //            IEnumerable<Package> top10 = null;
        //            if (count > decoupage)
        //                top10 = packages.Take(decoupage);
        //            else
        //                top10 = packages.Take(count);
        //            var start = top10.Min(x => x.DateFinTraitementLot.TotalMilliseconds);
        //            var stop = top10.Max(x => x.DateFinTraitementLot.TotalMilliseconds);

        //            j++;

        //            if (count > decoupage)
        //            {
        //                packages.RemoveRange(0, decoupage);
        //                sW.WriteLine(j + ";" + Convert.ToString(stop - start) + ";" + j * decoupage * tailleDeLots +
        //                    ";" + (decoupage * tailleDeLots / (stop - start) * 36000000));
        //            }
        //            else
        //            {
        //                packages.RemoveRange(0, count);
        //                sW.WriteLine(j + ";" + Convert.ToString(stop - start) + ";" + total +
        //                    ";" + (count / (stop - start) * 36000000));
        //            }

        //            termine = packages.Count == 0;
        //        }

        //        sR.Close();
        //        sW.Close();

        //        //int j = 0;
        //        //foreach (var item in packages)
        //        //{
        //        //    j++;
        //        //    sW.WriteLine(j + ";" + Convert.ToString(item.DateFinTraitementLot.TotalMilliseconds - item.DateDebutTraitementLot.TotalMilliseconds));
        //        //}
        //        //sW.Close();
        //    }
        //}
        
    }
}
